/*
  HardwareSerial.h - Hardware serial library for Wiring
  Copyright (c) 2006 Nicholas Zambetti.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

  Modified 28 September 2010 by Mark Sproul
  Modified 14 August 2012 by Alarus
  Modified 3 December 2013 by Matthijs Kooijman
*/

#ifndef HardwareSerial_h
#define HardwareSerial_h

#include <stdint.h>

#include "Arduino.h"
#include "Stream.h"
#include "CircularBuffer.h"

//#include "Platform/platform_config.h"

// Define constants and variables for buffering incoming serial data.  We're
// using a ring buffer (I think), in which head is the index of the location
// to which to write the next incoming character and tail is the index of the
// location from which to read.
// NOTE: a "power of 2" buffer size is recommended to dramatically
//       optimize all the modulo operations for ring buffers.
// WARNING: When buffer sizes are increased to > 256, the buffer index
// variables are automatically increased in size, but the extra
// atomicity guards needed for that are not implemented. This will
// often work, but occasionally a race condition can occur that makes
// Serial behave erratically. See https://github.com/arduino/Arduino/issues/2405
#if !defined(SERIAL_BUFFER_SIZE)
#define SERIAL_BUFFER_SIZE 64
#endif

typedef enum : uint8_t {
    SERIAL_EVENT_NONE    = 0x00,
    SERIAL_EVENT_IDLE    = 0x01,
    SERIAL_EVENT_TX_HALF = 0x02,
    SERIAL_EVENT_TX_TC   = 0x04,
    SERIAL_EVENT_RX_HALF = 0x08,
    SERIAL_EVENT_RX_TC   = 0x10,
} SerialEvent_t;

class HardwareSerial : public Stream {
    /* Serial user callback function define */
    typedef void (*Callback_t)(HardwareSerial *serial, SerialEvent_t event);

    typedef struct {
        bool          buys;     /* dma buys flag */
        SerialEvent_t event;    /* interrupt event */

        uint32_t last_transfer_cnt;  /* not half or complete transfer count */
        uint32_t buffer_size;

    } DmaState_t;

public:
    explicit HardwareSerial(void *uart, size_t bufferSize = SERIAL_BUFFER_SIZE, bool use_dma = true);
    ~HardwareSerial();

    void begin(uint32_t baud, Callback_t call, bool use_dma = true);

    void begin(uint32_t baud = 115200) { begin(baud, nullptr); }

    void end();

    virtual int available() { return (int) rxBuffer.available(); }
    int availableForWrite() { return (int) txBuffer.available(); }

    virtual int peek();
    virtual int read();
    virtual size_t read(uint8_t *buffer, size_t length);
    virtual size_t write(uint8_t);
    virtual size_t write(const uint8_t *buffer, size_t size);   /* data write to linked list */
    virtual void flush();
    virtual void flushForWrite();

    inline size_t write(unsigned long n) { return write((uint8_t) n); }

    inline size_t write(long n) { return write((uint8_t) n); }

    inline size_t write(unsigned int n) { return write((uint8_t) n); }

    inline size_t write(int n) { return write((uint8_t) n); }

//    using Print::write; // pull in write(str) and write(buf, size) from Print
//    operator bool() { return true; }

    void irqHandler(void *uart, int event, uint32_t idleRxCount);

private:
    /**
     * Startup DMA channel
     * @param written_len Last time channel transferred data size
     * @return If success startup dma return true, else return false
     */
    bool readFormUart(uint32_t written_len);
    bool writeToUart(uint32_t has_write_len); /* Used DMA transfer ll data*/

public:
    void *_uart;
    bool _isDMA;

    DmaState_t txDma;
    DmaState_t rxDma;
    Callback_t callback = nullptr;

    CircularBuffer txBuffer;
    CircularBuffer rxBuffer;

};

#if SERIAL_1_ENABLE
extern HardwareSerial Serial;
#endif

#if SERIAL_2_ENABLE
extern HardwareSerial Serial1;
#endif

#if SERIAL_3_ENABLE
extern HardwareSerial Serial3;
#endif

//extern void serialEventRun(void) __attribute__((weak));

#endif
